#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QHeaderView>
#include <QTimer>
#include <QDir>
#include <QTreeWidget>
#include <QPushButton>
#include <QTextStream>
#include <QUdpSocket>
#include <QTcpServer>
#include <QTcpSocket>
#include <QTimerEvent>
#include <QWidget>
#include <QAction>
#include <QStatusBar>
#include <QMenuBar>
#include <QToolBar>
#include <QSplitter>
#include <QLabel>
#include <QTreeWidgetItem>

#include <iostream>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <stdlib.h>

#include "fileserver.h"
#include "filesviewdelegate.h"
#include "tabwidgets.h"

using namespace std;

FileServer::FileServer(QWidget *parent)
    :QMainWindow(parent)
{
    createActions();
    createToolbars();
    createSplitter();
    createStatusBar();
    setWindowTitle("File Server");
    setFixedSize(320, 240);

    //create directory
    QDir *dir = new QDir();
    if(!dir->exists(QString(dataDir)))
    {
        //cout<<dir->absolutePath().toAscii().data()<<endl;
        dir->mkpath(QString(dataDir));
        //cout<<res<<endl;
    }
    delete dir;

    //create log file
    QFile *logfile = new QFile(QString(dataDir).append(logFileName));
    if(!(logfile->exists()))
    {
        logfile->open(QIODevice::WriteOnly);
        QTextStream out(logfile);
        out.setFieldWidth(20);
        out<<"File name"<<"Send count"<<"Receiver ID"<<"Send size"<<"Left size"<<endl;
        logfile->close();
    }
    logfile->deleteLater();

    connect(m_connect, SIGNAL(triggered()), this, SLOT(startServer()));
    connect(m_disallconnect, SIGNAL(triggered()), this, SLOT(closeAllTcpConnect()));
    connect(m_quit, SIGNAL(triggered()), this, SLOT(closeAll()));
    connect(m_checkLog, SIGNAL(triggered()), this, SLOT(checkLog()));
    //connect(filesview, SIGNAL(itemActivated(QTreeWidgetItem*, int)), this, SLOT(tabWidgetOn(QTreeWidgetItem *)));
    //connect(filesview, SIGNAL(activated(QModelIndex)), this, SLOT())
    //connect(filesview, SIGNAL(itemClicked(QTreeWidgetItem*, int)), this, SLOT(tabWidgetOn(QTreeWidgetItem *)));
    connect(filesview, SIGNAL(itemSelectionChanged()), this, SLOT(selectionChanged()));
}

void FileServer::startUdpServer()
{
    udpserver = new QUdpSocket(this);
    //bind UDP Server to the broadcast port
    udpserver->bind(BROADCASTPORT);
    std::cout<<"UDP Server started."<<std::endl;
    lTab->setlogTab(QTime::currentTime(), "UDP Server started.");
    statusBar()->showMessage("UDP Server started.", 3000);

    connect(udpserver, SIGNAL(readyRead()), this, SLOT(processUdpData()));
}

void FileServer::processUdpData()
{
    char buff[BUFFERSIZE];
    QHostAddress remoteadd;
    quint16 port;

    //receive a UPD datagram
    memset(buff, 0, sizeof(buff));
    int size = udpserver->readDatagram(buff, sizeof(buff), &remoteadd, &port);

    //get the random integer
    int randint;
    sscanf(buff + 1, "%d", &randint);

    if(0x01 == buff[0])
    {
        std::cout<<"receive beacon."<<std::endl;
        std::cout<<"random int: "<<randint<<std::endl;
        std::cout<<"remote address: "<<remoteadd.toString().toStdString()<<std::endl;
        statusBar()->showMessage("receive beacon...", 1500);
        statusBar()->showMessage(remoteadd.toString(), 1500);

        //create a reply to UPD datagram
        buff[0] = 0x02;

        QByteArray datagram(buff, size);
        if(udpserver->writeDatagram(datagram, remoteadd, UDPPORT) < 0)
        {
            std::cerr<<"< FileServer::processUdpData() > Error: "
                     <<qPrintable(udpserver->errorString())
                     <<std::endl;
            statusBar()->showMessage("processUdpData Error...");
        }
    }
}

void FileServer::startTcpServer()
{
    tcpserver = new QTcpServer(this);
    tcpserver->listen(QHostAddress::Any, PORT);
    std::cout<<"TCP server started."<<std::endl;
    statusBar()->showMessage("TCP server started...", 1500);
    lTab->setlogTab(QTime::currentTime(), "TCP server started.");

    connect(tcpserver,SIGNAL(newConnection()),this,SLOT(newTcpConnect()));
}

void FileServer::newTcpConnect()
{
    QTcpSocket *newConnect = tcpserver->nextPendingConnection();

    struct Job *job = new Job;
    job->client = newConnect;
    job->nextBlockSize = 0;
    job->timerID = startTimer(3000);

    //add the job to Jobs
    Jobs<<job;

    connect(newConnect,SIGNAL(readyRead()),this,SLOT(processTcpData()));
    connect(newConnect,SIGNAL(disconnected()),this,SLOT(jobTerminate()));
}

void FileServer::processTcpData()
{
    QTcpSocket *sender = qobject_cast<QTcpSocket *>(this->sender());

    int32_t bytes;
    char buff[BUFFERSIZE];

    int row = clientAtRow(sender);
    struct Job *job = Jobs.at(row);

    if(job->nextBlockSize == 0)
    {
        if(sender->bytesAvailable() < sizeof(int32_t))
        {
            return;
        }
        sender->read((char *)&(job->nextBlockSize), sizeof(job->nextBlockSize));
    }

    if(sender->bytesAvailable() < job->nextBlockSize)
    {
        return;
    }

    memset(buff, 0, sizeof(buff));
    bytes = sender->read(buff, job->nextBlockSize);

    killTimer(job->timerID);
    job->timerID = startTimer(3000);

    //std::cout<<"buff[0]: "<<(int)buff[0]<<std::endl;

    //It's a package contain information of file will be transfer
    if(buff[0] == 0x05)
    {
        std::cout<<"receive file status."<<std::endl;

        //initial a job
        job->filename.sprintf("%s", buff + 1);
        sscanf(buff + 2 + job->filename.length(), "%d,%d,%u,%d", &(job->sendCount), &(job->lastRecvId), &(job->sendBlock),&(job->size));

        job->filename.append("." + QString::number((sender->peerAddress().toIPv4Address())%256));
        job->filename.append(".part" + QString::number(job->sendCount + 1));
        std::cout<<"  filename    :"<<job->filename.toAscii().data()<<std::endl
                 <<"  send_count  :"<<job->sendCount<<std::endl
                 <<"  last_rcv_id :"<<job->lastRecvId<<std::endl
                 <<"  send byte   :"<<job->sendBlock<<std::endl
                 <<"  size byte   :"<<job->size<<std::endl;
        job->leftsize = job->size;
        job->file = new QFile(QString(dataDir).append(job->filename));
        //cout<<QString(dataDir).append(job->filename).toAscii().data()<<endl;

        QFile *logfile = new QFile(QString(dataDir).append(logFileName));
        QTextStream out(logfile);
        out.setFieldWidth(20);
        logfile->open(QIODevice::Append);
        int loc_end = job->filename.lastIndexOf(QRegExp("[0-9]"));
        int loc_start = job->filename.lastIndexOf("t");
        cout<<loc_end<<"  "<<loc_start<<endl;
        QString last_file(job->filename);
        last_file.resize(loc_start + 1);
        last_file.sprintf("%s%d", last_file.toAscii().data(), job->sendCount);
        cout<<"filename: "<<job->filename.toAscii().data()<<endl;
        cout<<"last file: "<<last_file.toAscii().data()<<endl;

        out<<last_file.toAscii().data()<<job->sendCount<<job->lastRecvId<<job->sendBlock<<job->size<<endl;
        logfile->close();
        logfile->deleteLater();

        //open the file to receive data
        if(!job->file->open(QIODevice::ReadWrite))
        {
            std::cout<<"cannot open file for reading:"
                     <<qPrintable(job->file->errorString())<<std::endl;
            return;
        }
        std::cout<<"open file: "<<job->filename.toStdString()<<std::endl;

        //create a item add add it to filesview
        QTreeWidgetItem *item = new QTreeWidgetItem(filesview);
        std::cout<<"QTreeWidgetItem create"<<std::endl;

        //set file name to filesview
        item->setIcon(0, QIcon(":/icons/connectarrow.png"));
        item->setText(0, job->filename);

        //fomate the size of file

        job->strsize.clear();
        if(job->size < 1024)
        {
            job->strsize = QString("%1").arg(job->size).append("B");
        }
        else if(job->size >= 1024 && job->size < 1024 * 1024)
        {
            job->strsize.sprintf("%.1fK", job->size * 1.0 / 1024);
        }
        else if(job->size >= 1024 * 1024 && job->size < 1024 * 1024 * 1024)
        {
            job->strsize.sprintf("%.1fM", job->size * 1.0 / 1024 / 1024);
        }
        else if(job->size >= 1024 * 1024 * 1024)
        {
            job->strsize.sprintf("%.1fG", job->size * 1.0 / 1024 / 1024 / 1024);
        }

        job->startTime = QTime::currentTime();

        item->setText(2, QString("0/").append(job->strsize));
        item->setText(3, QString("00:00"));
    }
    //It's a package containing data
    else if(buff[0] == 0x03)
    {
        //write data to file
        job->file->write(buff +1 , (quint64)(bytes - 1));
        job->file->flush();

        //statusBar()->showMessage(QString("receive %1 bytes").arg(QString::number(bytes-1)),1500);
        //lTab->setlogTab(QTime::currentTime(),QString("receive %1 bytes").arg(QString::number(bytes-1)));

        //update the receiving file information
        job->leftsize -= bytes - 1;

        //update progress of filesview
        QTreeWidgetItem *item = filesview->topLevelItem(row);
        if(item)
        {
            item->setText(1, QString::number(progress(row)));
            double leftprocess = job->strsize.mid(0, job->strsize.size() - 1).toDouble();
            QString str;
            str.sprintf("%.1f/",leftprocess * (1 - job->leftsize * 1.0 / job->size)).append(job->strsize);
            item->setText(2,str);
            QTime ctime = QTime::currentTime();

            //std::cout<<"startTime: "<<job->startTime.toString().toAscii().data()<<std::endl
            //         <<"ctime: "<<ctime.toString().toAscii().data()<<std::endl;
            int sec = (ctime.minute() - job->startTime.minute()) % 60 * 60 + ctime.second() - job->startTime.second();
            //std::cout<<"sec: "<<sec<<std::endl;
            item->setText(3, QString().sprintf("%02d:%02d",sec / 60, sec % 60));
        }
    }
    else if(buff[0] == 0x04)
    {
        cout<<"end"<<endl;
        QFile *logfile = new QFile(QString(dataDir).append(logFileName));
        QTextStream out(logfile);
        logfile->open(QIODevice::Append);
        int size = job->filename.size();
        int locate = job->filename.lastIndexOf("part");
        job->filename.replace(locate, size - locate, "end");
        out.setFieldWidth(20);
        out<<job->filename.toAscii().data()<<job->sendCount + 1<<job->client->localAddress().toIPv4Address()%256<<job->sendBlock + job->size<<"0"<<endl;
        job->file->rename(QString(dataDir).append(job->filename));
        logfile->close();
        logfile->deleteLater();
    }

    bytes += sizeof(int32_t);
    sender->write((char *)&bytes, sizeof(bytes));

    job->nextBlockSize = 0;
}

int FileServer::clientAtRow(QTcpSocket *client)
{
    int i = 0;
    foreach(Job *job,Jobs)
    {
        if(job->client == client)
            return i;
        i++;
    }
    return -1;
}

void FileServer::closeAllTcpConnect()
{
    if(!tabWidget->isHidden())
    {
        tabWidget->hide();
    }
    foreach(Job *job, Jobs)
    {
        job->client->disconnectFromHost();
    }
    delete udpserver;
    delete tcpserver;
    std::cout<<"UDP Server terminate."<<std::endl
             <<"TCP Server terminate."<<std::endl;
    m_disallconnect->setDisabled(true);
    m_connect->setEnabled(true);
    statusBar()->showMessage("TCP server terminate...", 1500);
}

int FileServer::progress(int row)
{
    struct Job *job = Jobs.at(row);
    if(job->size > 0)
    {
        //std::cout<<"size: "<<job->size<<" left size: "<<job->leftsize<<std::endl;
        return 100 - (job->leftsize * 1.0 / job->size) * 100;
    }
    return 100;
}

void FileServer::jobTerminate()
{
    QTcpSocket *sender = qobject_cast<QTcpSocket *>(this->sender());
    int row = clientAtRow(sender);

    struct Job *job = Jobs.at(row);
    job->client->disconnectFromHost();
    job->client->deleteLater();
    job->file->close();
    job->file->deleteLater();
    delete job;

    delete filesview->topLevelItem(row);
    Jobs.removeAt(row);
    std::cout<<"job complete"<<std::endl;
}

void FileServer::startServer()
{
    statusBar()->showMessage("scanning...",1500);
    lTab->setlogTab(QTime::currentTime(),"Start scanning..");
    startUdpServer();
    startTcpServer();
    m_connect->setDisabled(true);
    m_disallconnect->setEnabled(true);
}

void FileServer::timerEvent(QTimerEvent *event)
{
    killTimer(event->timerId());

    int row = 0;
    foreach(Job *job, Jobs)
    {
        if(event->timerId() == job->timerID)
        {
            cout<<job->client->peerAddress().toString().toAscii().data()<<" timeout"<<endl;
            job->client->disconnectFromHost();

            break;
        }

        row++;
    }
}

void FileServer::createActions()
{
    m_connect = new QAction(QIcon(":/icons/connect.png"), tr("&Scan"), this);
    m_disallconnect = new QAction(QIcon(":/icons/disconnect.png"), tr("&Stop"), this);
    m_disallconnect->setDisabled(true);
    m_quit = new QAction(QIcon(":/icons/quit.png"), tr("&Quit"), this);
    m_checkLog= new QAction(QIcon(":icons/log.png"), tr("&Log"), this);
}

void FileServer::createToolbars()
{
    topBar=addToolBar(tr("Tools"));
    addToolBar(Qt::TopToolBarArea,topBar);
    topBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
    topBar->setMovable(false);
    topBar->addAction(m_connect);
    topBar->addAction(m_disallconnect);
    topBar->addAction(m_quit);
    topBar->addAction(m_checkLog);

    topBar->setFixedSize(320,30);
}

void FileServer::createSplitter()
{
    //create TreeWidget
    filesview = new QTreeWidget(this);
    filesview->setMinimumHeight(80);
    QStringList headers;
    headers<<"File name"<<"Progress"<<"Size"<<"Time";
    filesview->setHeaderLabels(headers);
    filesview->setItemDelegate(new FilesViewDelegate(this));
    filesview->setSelectionBehavior(QAbstractItemView::SelectRows);
    filesview->setAlternatingRowColors(true);
    filesview->setRootIsDecorated(false);
    filesview->setContentsMargins(0,0,0,0);

    // Set header resize modes and initial section sizes
    QFontMetrics fm = fontMetrics();
    QHeaderView *header = filesview->header();
    header->resizeSection(0, fm.width("data00.205.part01"));
    header->resizeSection(1,fm.width("Progress"));
    header->resizeSection(2, fm.width("1023.9/1023.9M"));
    header->resizeSection(3, fm.width("00:00"));

    //Add tabwidget embbed_in splitter by wayne
    tabWidget = new QTabWidget(this);
    gTab = new GeneralTab(this);
    lTab = new logTab(this);
    tabWidget->addTab(gTab, tr("Gen"));
    tabWidget->addTab(lTab, tr("Log"));
    tabWidget->setTabPosition(QTabWidget::West);
    tabWidget->setMaximumHeight(240);
    tabWidget->setContentsMargins(0, 1, 0, 1);
    tabWidget->hide();

    //Set splitter window
    splitter = new QSplitter(Qt::Vertical, this);
    splitter->addWidget(filesview);
    splitter->addWidget(tabWidget);
    splitter->setStretchFactor(0, 1);
    splitter->setStretchFactor(1, 2);
    setCentralWidget(splitter);
    centralWidget()->sizeHint();
}

void FileServer::createStatusBar()
{
    tipLabel= new QLabel;
    tipLabel->setAlignment(Qt::AlignRight);
    statusBar()->addPermanentWidget(tipLabel);
    statusBar()->showMessage("Ready...");
    statusBar()->setMinimumHeight(20);
    statusBar()->setStyleSheet(QString("QStatusBar::item{border: 0px}"));
}

void FileServer::tabWidgetOn(QTreeWidgetItem *clickedItem)
{
    int getIndex=filesview->indexOfTopLevelItem(clickedItem);

    gTab->setGeneralTab(Jobs.at(getIndex)->filename,Jobs.at(getIndex)->lastRecvId);
    QString ipWithPort=QString("%1:%2").arg(Jobs.at(getIndex)->client->peerAddress().toString())\
            .arg(QString::number(Jobs.at(getIndex)->client->peerPort()));
    tipLabel->setText(ipWithPort);

    //topBar->hide();
    tabWidget->show();
    menuBar()->show();

    cout<<"tabWidgetOn show"<<endl;
}

void FileServer::tabWidgetOff()
{
    if(!tabWidget->isHidden())
    {
        tabWidget->hide();
        topBar->show();
        menuBar()->hide();
    }
}

void FileServer::checkLog()
{
    if(tabWidget->isHidden())
    {
        filesview->setCurrentItem(filesview->topLevelItem(0));
        tabWidget->setCurrentWidget(lTab);
        if(filesview->columnCount())
        tabWidget->show();
    }
    else
    {
        tabWidget->hide();
    }
}

void FileServer::selectionChanged()
{
    cout<<"selectionChanged"<<endl;
    QList<QTreeWidgetItem *> list = filesview->selectedItems();
    cout<<"size of list "<<list.size()<<endl;
    if(list.size() > 0)
    {
        tabWidgetOn(list.at(0));
    }
    else
    {
        tabWidget->hide();
    }
}

void FileServer::closeAll()
{
    this->close();
    system("qtopia &");
}
